/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.ericsson.nfc.jsr257.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Vector;
import my.javax.microedition.contactless.ndef.NDEFRecord;
import my.javax.microedition.contactless.ndef.NDEFRecordType;

/**
 *
 * @author emarkki
 */
public class NDEFMessageImpl {

    public static byte[] toRecordByteArray(NDEFRecord record) {
        return encode(record, MB|ME);
    }

    private static byte[] encode(NDEFRecord record, int mBmE) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        
        int tnf;
        String sType;
        switch(record.getRecordType().getType()) {
            case NDEFRecordType.EMPTY:
                sType = "";
                tnf = 0x00;
                break;
            case NDEFRecordType.NFC_FORUM_RTD:
                sType = record.getRecordType().getName();
                tnf = 0x01;
                break;
            case NDEFRecordType.MIME:
                sType = record.getRecordType().getName();
                tnf = 0x02;
                break;
            case NDEFRecordType.URI:
                sType = record.getRecordType().getName();
                tnf = 0x03;
                break;
            case NDEFRecordType.EXTERNAL_RTD:
                sType = record.getRecordType().getName();
                tnf = 0x04;
                break;
            case NDEFRecordType.UNKNOWN:
            default:
                sType = record.getRecordType().getName();
                tnf = 0x05;
        }
        //System.out.println("sType: " + sType);

        int hdr = mBmE | 0 | 0 | IL | tnf;
        out.write(hdr);
        
        byte[] type = sType.getBytes();
        out.write(type.length);
        //System.out.println("encode type-len: " + type.length);
        
        int payloadLength = record.getPayloadLength();

        byte[] payloadLengthArray = new byte[4];
//                in.read(payloadLengthArray, 0, payloadLengthArray.length);
        
        payloadLengthArray[0] = (byte)((payloadLength >> 24)& 0xff);
        payloadLengthArray[1] = (byte)((payloadLength >> 16)& 0xff);
        payloadLengthArray[2] = (byte)((payloadLength >> 8)& 0xff);
        payloadLengthArray[3] = (byte)(payloadLength & 0xff);
        out.write(payloadLengthArray, 0, payloadLengthArray.length);
        
        //System.out.println("payloadLength="+payloadLength);
        
        //System.out.println("payloadArray: "+Utils.toFixedHexString(payloadLengthArray));

        int idLength = 0;
        if(record.getId() != null) {
            idLength = record.getId().length;
        }
        out.write(idLength);

//        byte[] type = record.getRecordType().getName().getBytes();
        out.write(type, 0, type.length);

        byte[] id = record.getId();
        if(id != null) {
            out.write(id, 0, id.length);
        }

        byte[] payload = record.getPayload();
        if(payload != null) {
            out.write(payload, 0, payload.length);
        }
        
        return out.toByteArray();
    }

    private NDEFRecord[] records;
    
    public NDEFMessageImpl(NDEFRecord[] records) {
        this.records = records;
    }

    /**
     * Creates an NDEF message from the byte array starting from the specified offset in the byte array.
     * @param data
     * @param offset
     */
    public NDEFMessageImpl(byte[] data, int offset) {
        decode(data, offset);
    }

    public int getNumberOfRecords() {
        return records.length;
    }

    public NDEFRecord getRecord(int i) {
        return records[i];
    }

    public NDEFRecord[] getRecords() {
        return records;
    }

    public byte[] toByteArray() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for(int i = 0; i < records.length; i++) {
            int hdr = 0;
            if(i == 0) {
                hdr |= MB;
            }
            if(i == records.length-1) {
                hdr |= ME;
            }
            byte[] record = encode(records[i], hdr);
            out.write(record, 0, record.length);
        }
        return out.toByteArray();
    }

    private static final int MB = 0x80;
    private static final int ME = 0x40;
    private static final int CF = 0x20;
    private static final int SR = 0x10;
    private static final int IL = 0x08;
    private static final int TNF_MASK = 0x07;
    
    private void decode(byte[] data, int offset) {
        ByteArrayInputStream in = new ByteArrayInputStream(data, offset, data.length);
        
        Vector recs = new Vector();
        
        int hdr;
        
        do {
            
            hdr = in.read();
            //System.out.println("header: " + hdr);
            //System.out.println("ME=" + ((hdr&ME) != 0));
            
            int typeLength = in.read();
            //System.out.println("typeLength="+typeLength);

            int payloadLength;

            if((hdr & SR) != 0) {
                payloadLength = in.read();
            } else {
                byte[] payloadLengthArray = new byte[4];
                in.read(payloadLengthArray, 0, payloadLengthArray.length);
                //System.out.println("payloadArray to parse: " + Utils.toFixedHexString(payloadLengthArray));
                payloadLength = (int)(payloadLengthArray[0] << 24) + (int)(payloadLengthArray[1] << 16) + (int)(payloadLengthArray[2] << 8) + (int)(payloadLengthArray[3] & 0xff);
//                int test = (payloadLengthArray[2] << 8);
//                System.out.println("test: "+test);
            }
            //System.out.println("payloadLength="+payloadLength);

            payloadLength &= 0x1FFFFFFF; // TODO: Ful-hack
            
            int idLength;
            if((hdr & IL) != 0) {
                idLength = in.read();
            } else {
                idLength = 0;
            }

            byte[] type = new byte[typeLength];
            in.read(type, 0, type.length);

            byte[] id = new byte[idLength];
            in.read(id, 0, id.length);

            byte[] payload = new byte[payloadLength];
            in.read(payload, 0, payload.length);

            //System.out.println("TNF:" + (hdr&TNF_MASK));

            NDEFRecordType recordType;
            switch(hdr & TNF_MASK) {
                case 0x00:
                    recordType = new NDEFRecordType(NDEFRecordType.EMPTY, "");
                    break;
                case 0x01:
                    recordType = new NDEFRecordType(NDEFRecordType.NFC_FORUM_RTD, "urn:nfc:wkt:"+new String(type));
                    break;
                case 0x02:
                    recordType = new NDEFRecordType(NDEFRecordType.MIME, new String(type));
                    break;
                case 0x03:
                    recordType = new NDEFRecordType(NDEFRecordType.URI, new String(type));
                    break;
                case 0x04:
                    recordType = new NDEFRecordType(NDEFRecordType.EXTERNAL_RTD, "urn:nfc:ext:"+new String(type));
                    break;
                case 0x06:
                    throw new RuntimeException("Record type 'unchanged' not implemented!");
                case 0x05:
                default:
                    recordType = new NDEFRecordType(NDEFRecordType.UNKNOWN, new String(type));
                    break;
            }
            //System.out.println("recordType: " + recordType);

            recs.addElement(new NDEFRecord(recordType, id, payload));
        } while((hdr & ME) == 0);
        
        records = new NDEFRecord[recs.size()];
        for(int i = 0; i < recs.size(); i++) {
            records[i] = (NDEFRecord) recs.elementAt(i);
        }
        
    }

}
